package com.example.shortlink.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import com.example.shortlink.common.biz.user.UserContext;
import com.example.shortlink.common.convention.exception.ClientException;
import com.example.shortlink.dao.entity.TGroup;
import com.example.shortlink.dao.mapper.TGroupMapper;
import com.example.shortlink.dto.req.ShortLinkGroupSortReqDTO;
import com.example.shortlink.dto.req.ShortLinkGroupUpdateReqDTO;
import com.example.shortlink.dto.resp.ShortLinkGroupRespDTO;
import com.example.shortlink.service.GroupService;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

import static com.example.shortlink.common.constant.RedisCacheConstant.LOCK_GROUP_CREATE_KEY;
import static com.example.shortlink.dao.entity.table.TGroupTableDef.T_GROUP;

@Slf4j
@Service
@RequiredArgsConstructor
public class GroupServiceImpl extends ServiceImpl<TGroupMapper, TGroup> implements GroupService {

    private final RedissonClient redissonClient;

    @Value("${short-link.group.max-num}")
    private Integer groupMaxNum;

    @Override
    public void saveGroup(String name) {
        log.info("saveGroup param name:{}", name);
        this.saveGroup(UserContext.getUsername(), name);
    }

    /**
     * 保存组
     *
     * @param username  用户名
     * @param groupName 组名
     */
    public void saveGroup(String username, String groupName) {
        RLock lock = redissonClient.getLock(String.format(LOCK_GROUP_CREATE_KEY, username));
        lock.lock();
        try {
            List<TGroup> groupDOList = QueryChain.of(mapper)
                    .where(T_GROUP.USERNAME.eq(username)
                            .and(T_GROUP.DEL_FLAG.eq(Boolean.FALSE)))
                    .list();
            if (CollUtil.isNotEmpty(groupDOList) && groupDOList.size() == groupMaxNum) {
                throw new ClientException(String.format("已超出最大分组数:%d", groupMaxNum));
            }
            String gid;
            do {
                gid = IdUtil.nanoId(10);
            } while (!hasGid(username, gid));
            TGroup group = TGroup.builder()
                    .gid(gid)
                    .sortOrder(0)
                    .username(username)
                    .name(groupName)
                    .build();
            this.save(group);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 查询组列表
     *
     * @return 组列表
     */
    @Override
    public List<ShortLinkGroupRespDTO> listGroup() {
        log.info("listGroup param");
        List<ShortLinkGroupRespDTO> shortLinkGroupRespDTOList = QueryChain.of(mapper)
                .select(T_GROUP.GID.as(ShortLinkGroupRespDTO::getGid),
                        T_GROUP.NAME.as(ShortLinkGroupRespDTO::getName),
                        T_GROUP.SORT_ORDER.as(ShortLinkGroupRespDTO::getSortOrder))
                .from(T_GROUP)
                .where(T_GROUP.DEL_FLAG.eq(Boolean.FALSE)
                        .and(T_GROUP.USERNAME.eq(UserContext.getUsername())))
                .orderBy(T_GROUP.SORT_ORDER.desc(), T_GROUP.UPDATE_TIME.desc())
                .listAs(ShortLinkGroupRespDTO.class);
        // TODO 远程调用查询分组下短链接数量
        shortLinkGroupRespDTOList.forEach(each -> {

        });
        return shortLinkGroupRespDTOList;
    }

    /**
     * 修改短链接分组名称
     *
     * @param requestParam 短链接分组名称修改请求参数
     */
    @Override
    public void updateGroup(ShortLinkGroupUpdateReqDTO requestParam) {
        log.info("updateGroup param requestParam:{}", requestParam);
        boolean update = UpdateChain.of(TGroup.class)
                .set(T_GROUP.NAME, requestParam.getName())
                .where(T_GROUP.USERNAME.eq(UserContext.getUsername())
                        .and(T_GROUP.GID.eq(requestParam.getGid()))
                        .and(T_GROUP.DEL_FLAG.eq(Boolean.FALSE)))
                .update();
        log.info("updateGroup update flag:{}", update);
    }

    /**
     * 删除短链接分组
     *
     * @param gid 组ID
     */
    @Override
    public void deleteGroup(String gid) {
        log.info("deleteGroup param gid:{}", gid);
        boolean delete = UpdateChain.of(TGroup.class)
                .set(T_GROUP.DEL_FLAG, Boolean.TRUE)
                .where(T_GROUP.USERNAME.eq(UserContext.getUsername())
                        .and(T_GROUP.GID.eq(gid))
                        .and(T_GROUP.DEL_FLAG.eq(Boolean.FALSE)))
                .update();
        log.info("updateGroup delete flag:{}", delete);
    }

    /**
     * 排序短链接分组
     *
     * @param requestParam 排序列表
     */
    @Override
    public void sortGroup(List<ShortLinkGroupSortReqDTO> requestParam) {
        log.info("sortGroup param requestParam:{}", requestParam);
        // TODO 修改循环中因条件不同进行的更新
        requestParam.forEach(each -> {
            boolean update = UpdateChain.of(TGroup.class)
                    .set(T_GROUP.SORT_ORDER, each.getSortOrder())
                    .where(T_GROUP.USERNAME.eq(UserContext.getUsername())
                            .and(T_GROUP.GID.eq(each.getGid()))
                            .and(T_GROUP.DEL_FLAG.eq(Boolean.FALSE)))
                    .update();
            log.info("sortGroup update flag:{}", update);
        });
    }

    /**
     * 判断gid是否存在
     *
     * @param username 用户名
     * @param gid      组id
     * @return 存在返回 false，不存在返回 true
     */
    private boolean hasGid(String username, String gid) {
        TGroup group = QueryChain.of(mapper)
                .select(T_GROUP.DEFAULT_COLUMNS)
                .from(T_GROUP)
                .where(T_GROUP.GID.eq(gid)
                        .and(T_GROUP.USERNAME.eq(Optional.ofNullable(username).orElse(UserContext.getUsername()))))
                .oneOpt().orElse(null);
        return null == group;
    }
}
